Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.79% covered (success)
95.79%
91 / 95
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Serializer
95.79% covered (success)
95.79%
91 / 95
66.67% covered (warning)
66.67%
4 / 6
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 normalize
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
12.05
 denormalizeOnMethodCall
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 denormalizeNewObject
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
11.04
 denormalizeOnExistingObject
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Serializer;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Exceptions\InvalidTypeException;
6use Apie\Core\Lists\ItemHashmap;
7use Apie\Core\Lists\ItemList;
8use Apie\Core\Metadata\Concerns\UseContextKey;
9use Apie\Core\Metadata\MetadataFactory;
10use Apie\Core\Utils\ConverterUtils;
11use Apie\Core\ValueObjects\Utils;
12use Apie\Serializer\Context\ApieSerializerContext;
13use Apie\Serializer\Context\NormalizeChildGroup;
14use Apie\Serializer\Exceptions\ValidationException;
15use Apie\Serializer\Lists\NormalizerList;
16use Apie\Serializer\Normalizers\BooleanNormalizer;
17use Apie\Serializer\Normalizers\DateTimeNormalizer;
18use Apie\Serializer\Normalizers\DateTimeZoneNormalizer;
19use Apie\Serializer\Normalizers\DoNotChangeFileNormalizer;
20use Apie\Serializer\Normalizers\EnumNormalizer;
21use Apie\Serializer\Normalizers\FloatNormalizer;
22use Apie\Serializer\Normalizers\IdentifierNormalizer;
23use Apie\Serializer\Normalizers\IntegerNormalizer;
24use Apie\Serializer\Normalizers\ItemListNormalizer;
25use Apie\Serializer\Normalizers\PaginatedResultNormalizer;
26use Apie\Serializer\Normalizers\PolymorphicObjectNormalizer;
27use Apie\Serializer\Normalizers\ReflectionTypeNormalizer;
28use Apie\Serializer\Normalizers\ResourceNormalizer;
29use Apie\Serializer\Normalizers\StringableCompositeValueObjectNormalizer;
30use Apie\Serializer\Normalizers\StringNormalizer;
31use Apie\Serializer\Normalizers\UploadedFileNormalizer;
32use Apie\Serializer\Normalizers\ValueObjectNormalizer;
33use Exception;
34use Psr\Http\Message\UploadedFileInterface;
35use ReflectionClass;
36use ReflectionMethod;
37
38class Serializer
39{
40    use UseContextKey;
41
42    public function __construct(private NormalizerList $normalizers)
43    {
44    }
45
46    public static function create(): self
47    {
48        return new self(new NormalizerList([
49            new PaginatedResultNormalizer(),
50            new DoNotChangeFileNormalizer(),
51            new UploadedFileNormalizer(),
52            new IdentifierNormalizer(),
53            new StringableCompositeValueObjectNormalizer(),
54            new PolymorphicObjectNormalizer(),
55            new DateTimeNormalizer(),
56            new DateTimeZoneNormalizer(),
57            new ResourceNormalizer(),
58            new EnumNormalizer(),
59            new ValueObjectNormalizer(),
60            new StringNormalizer(),
61            new IntegerNormalizer(),
62            new FloatNormalizer(),
63            new BooleanNormalizer(),
64            new ItemListNormalizer(),
65            new ReflectionTypeNormalizer(),
66        ]));
67    }
68
69    public function normalize(mixed $object, ApieContext $apieContext, bool $forceDefaultNormalization = false): string|int|float|bool|ItemList|ItemHashmap|null
70    {
71        $serializerContext = new ApieSerializerContext($this, $apieContext);
72        if (!$forceDefaultNormalization) {
73            foreach ($this->normalizers->iterateOverNormalizers() as $normalizer) {
74                if ($normalizer->supportsNormalization($object, $serializerContext)) {
75                    return $normalizer->normalize($object, $serializerContext);
76                }
77            }
78        }
79        if (is_array($object)) {
80            $count = 0;
81            $returnValue = [];
82            $isList = true;
83            foreach ($object as $key => $value) {
84                if ($key === $count) {
85                    $count++;
86                } else {
87                    $isList = false;
88                }
89                $returnValue[$key] = $serializerContext->normalizeChildElement($key, $value);
90            }
91            return $isList ? new ItemList($returnValue) : new ItemHashmap($returnValue);
92        }
93        if (!is_object($object)) {
94            if (in_array(get_debug_type($object), ['resource', 'resource (closed)'])) {
95                throw new InvalidTypeException($object, 'primitive');
96            }
97            return $object;
98        }
99        $metadata = MetadataFactory::getResultMetadata(new ReflectionClass($object), $apieContext);
100        $returnValue = [];
101
102        foreach ($metadata->getHashmap()->filterOnContext($apieContext, getters: true) as $fieldName => $metadata) {
103            if ($metadata->isField()) {
104                $returnValue[$fieldName] = $serializerContext->normalizeChildElement(
105                    $fieldName,
106                    $metadata->getValue($object, $apieContext)
107                );
108            }
109        }
110        return new ItemHashmap($returnValue);
111    }
112
113    public function denormalizeOnMethodCall(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $input, ?object $object, ReflectionMethod $method, ApieContext $apieContext): mixed
114    {
115        $serializerContext = new ApieSerializerContext($this, $apieContext);
116        try {
117            $arguments = $serializerContext->denormalizeFromMethod($input, $method);
118        } catch (Exception $error) {
119            throw ValidationException::createFromArray(['' => $error]);
120        }
121        return $method->invokeArgs($object, $arguments);
122    }
123
124    public function denormalizeNewObject(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $object, string $desiredType, ApieContext $apieContext): mixed
125    {
126        if (is_array($object)) {
127            $isList = false;
128            if ($desiredType === 'mixed') {
129                $isList = true;
130                $count = 0;
131                foreach (array_keys($object) as $key) {
132                    if ($key === $count) {
133                        $count++;
134                    } else {
135                        $isList = false;
136                        break;
137                    }
138                }
139            }
140            $object = $isList ? new ItemList($object) : new ItemHashmap($object);
141        }
142        if ($desiredType === 'mixed') {
143            return $object;
144        }
145        $serializerContext = new ApieSerializerContext($this, $apieContext);
146        foreach ($this->normalizers->iterateOverDenormalizers() as $denormalizer) {
147            if ($denormalizer->supportsDenormalization($object, $desiredType, $serializerContext)) {
148                return $denormalizer->denormalize($object, $desiredType, $serializerContext);
149            }
150        }
151        $refl = ConverterUtils::toReflectionClass($desiredType);
152        if (!$refl || !$refl->isInstantiable()) {
153            throw new InvalidTypeException($desiredType, 'a instantiable object');
154        }
155        $metadata = MetadataFactory::getCreationMetadata(
156            $refl,
157            $apieContext
158        );
159        $group = new NormalizeChildGroup(
160            $serializerContext,
161            $metadata
162        );
163        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
164        return $normalizedData->createNewObject();
165    }
166
167    public function denormalizeOnExistingObject(ItemHashmap $object, object $existingObject, ApieContext $apieContext): mixed
168    {
169        $refl = new ReflectionClass($existingObject);
170        $serializerContext = new ApieSerializerContext($this, $apieContext);
171        $metadata = MetadataFactory::getModificationMetadata(
172            $refl,
173            $apieContext
174        );
175        $group = new NormalizeChildGroup(
176            $serializerContext,
177            $metadata
178        );
179        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
180        return $normalizedData->modifyExistingObject($existingObject);
181    }
182}